#include "TextureManager.h"

#include <fstream>
#include <cstdint>

#include <D3DX10Math.h>
#include <D3DX10Tex.h>
#include <D3D10.h>

#include "D3DErrorChecker.h"
#include "MathUtils.h"

namespace D3D10Utils
{
	TextureManager& GetTextureManager()
	{
		static TextureManager textureManager;

		return textureManager;
	}

	TextureManager::TextureManager()
		: mDevice(nullptr)
		, mRandomTextureRV(nullptr)	
	{
	}

	TextureManager::~TextureManager()
	{
		std::map<std::wstring, ID3D10ShaderResourceView*>::iterator it;
        std::wstring sarasa;
		for(it = mTexturesRV.begin(); it != mTexturesRV.end(); ++it)
        {
            sarasa = (*it).first;
            ID3D10ShaderResourceView* sec = (*it).second;
			(*it).second->Release();
        }
		mRandomTextureRV->Release();
	}

	void TextureManager::init(ID3D10Device * const device)
	{
		mDevice = device;

		buildRandomTex();
	}

	ID3D10ShaderResourceView* TextureManager::getTextureRV(const std::wstring& filename)
	{
		ID3D10ShaderResourceView* &texture = mTexturesRV[filename];
		
		if (!texture) 
		{			
			HRESULT hr = D3DX10CreateShaderResourceViewFromFile(mDevice, filename.c_str(), 0, 0, &texture, 0);
			ErrorChecker(hr);
		}
		
		return texture;
	}

	ID3D10ShaderResourceView* TextureManager::getTextureArrayRV(const std::wstring& arrayName, const std::vector<std::wstring>& filenames)
	{
		ID3D10ShaderResourceView* &textureArrayRV = mTexturesRV[arrayName];
	
		if (!textureArrayRV) 
		{
			// Load the texture elements individually from file. These textures
			// won't be used by the GPU (0 bind flags), they are just used to 
			// load the image data from file.  We use the STAGING usage so the
			// CPU can read the resource.
			uint32_t arraySize = static_cast<uint32_t> (filenames.size());

			std::vector<ID3D10Texture2D*> sourceTextures(arraySize, 0);
			D3DX10_IMAGE_LOAD_INFO loadInfo;
			for(uint32_t i = 0; i < arraySize; ++i)
			{
				loadInfo.Width = D3DX10_FROM_FILE;
        		loadInfo.Height = D3DX10_FROM_FILE;
        		loadInfo.Depth = D3DX10_FROM_FILE;
        		loadInfo.FirstMipLevel = 0;
        		loadInfo.MipLevels = D3DX10_FROM_FILE;
        		loadInfo.Usage = D3D10_USAGE_STAGING;
        		loadInfo.BindFlags = 0;
        		loadInfo.CpuAccessFlags = D3D10_CPU_ACCESS_WRITE | D3D10_CPU_ACCESS_READ;
        		loadInfo.MiscFlags = 0;
        		loadInfo.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        		loadInfo.Filter = D3DX10_FILTER_NONE;
        		loadInfo.MipFilter = D3DX10_FILTER_NONE;
				loadInfo.pSrcInfo = 0;

        		HRESULT hr = D3DX10CreateTextureFromFile(mDevice, filenames[i].c_str(), 
					&loadInfo, 0, reinterpret_cast<ID3D10Resource**> (&sourceTextures[i]), 0);
				
				ErrorChecker(hr);
			}

			// Create the texture array. Each element in the texture 
			// array has the same format/dimensions.
			D3D10_TEXTURE2D_DESC textureElementDesc;
			sourceTextures[0]->GetDesc(&textureElementDesc);

			D3D10_TEXTURE2D_DESC textureArrayDesc;
			textureArrayDesc.Width = textureElementDesc.Width;
			textureArrayDesc.Height = textureElementDesc.Height;
			textureArrayDesc.MipLevels = textureElementDesc.MipLevels;
			textureArrayDesc.ArraySize = arraySize;
			textureArrayDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
			textureArrayDesc.SampleDesc.Count = 1;
			textureArrayDesc.SampleDesc.Quality = 0;
			textureArrayDesc.Usage = D3D10_USAGE_DEFAULT;
			textureArrayDesc.BindFlags = D3D10_BIND_SHADER_RESOURCE;
			textureArrayDesc.CPUAccessFlags = 0;
			textureArrayDesc.MiscFlags = 0;

			ID3D10Texture2D *textureArray = 0;
			HRESULT hr = mDevice->CreateTexture2D(&textureArrayDesc, 0, &textureArray);
			ErrorChecker(hr);
			
			// Copy individual texture elements into texture array.
			// for each texture element...
			for(uint32_t i = 0; i < arraySize; ++i)
			{
				// for each mipmap level...
				for(uint32_t j = 0; j < textureElementDesc.MipLevels; ++j)
				{
					D3D10_MAPPED_TEXTURE2D mappedTex2D;
					sourceTextures[i]->Map(j, D3D10_MAP_READ, 0, &mappedTex2D);
                    
       	     		mDevice->UpdateSubresource(textureArray, 
						D3D10CalcSubresource(j, i, textureElementDesc.MipLevels),
       	         		0, mappedTex2D.pData, mappedTex2D.RowPitch, 0);

            		sourceTextures[i]->Unmap(j);
				}
			}	
			
			// Create a resource view to the texture array.			
			D3D10_SHADER_RESOURCE_VIEW_DESC resourceViewDesc;
			resourceViewDesc.Format = textureArrayDesc.Format;
			resourceViewDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2DARRAY;
			resourceViewDesc.Texture2DArray.MostDetailedMip = 0;
			resourceViewDesc.Texture2DArray.MipLevels = textureArrayDesc.MipLevels;
			resourceViewDesc.Texture2DArray.FirstArraySlice = 0;
			resourceViewDesc.Texture2DArray.ArraySize = arraySize;

			ID3D10ShaderResourceView *newTextureArrayRV = 0;
			hr = mDevice->CreateShaderResourceView(textureArray, &resourceViewDesc, &newTextureArrayRV);
			ErrorChecker(hr);

			// Cleanup--we only need the resource view.
			textureArray->Release();

			for(uint32_t i = 0; i < arraySize; ++i)
				sourceTextures[i]->Release(); 

			textureArrayRV = newTextureArrayRV;
		}

		return textureArrayRV;
	}

	ID3D10ShaderResourceView* TextureManager::getCubeTextureRV(const std::wstring& filename)
	{
		ID3D10ShaderResourceView* &cubeTexture = mTexturesRV[filename];
	
		if (!cubeTexture) 
		{
			// Create it.
			D3DX10_IMAGE_LOAD_INFO loadInfo;
    		loadInfo.MiscFlags = D3D10_RESOURCE_MISC_TEXTURECUBE;

			ID3D10Texture2D* texture = 0;
			HRESULT hr = D3DX10CreateTextureFromFile(mDevice, filename.c_str(), 
				&loadInfo, 0, reinterpret_cast<ID3D10Resource**> (&texture), 0);
			
			ErrorChecker(hr);

    		D3D10_TEXTURE2D_DESC textureDesc;
			texture->GetDesc(&textureDesc);

    		D3D10_SHADER_RESOURCE_VIEW_DESC resourceViewDesc;
    		resourceViewDesc.Format = textureDesc.Format;
    		resourceViewDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURECUBE;
    		resourceViewDesc.TextureCube.MipLevels = textureDesc.MipLevels;
    		resourceViewDesc.TextureCube.MostDetailedMip = 0;
    
			ID3D10ShaderResourceView *resourceView = 0;
			hr = mDevice->CreateShaderResourceView(texture, &resourceViewDesc, &resourceView);
   			ErrorChecker(hr);
		
			texture->Release();

			cubeTexture = resourceView;
		}		
		
		return cubeTexture;
	}

	void TextureManager::buildRandomTex()
	{
		// Create the random data.
		D3DXVECTOR4* randomValues = new D3DXVECTOR4[1024];

		for(int i = 0; i < 1024; ++i)
		{
			randomValues[i].x = RandF(-1.0f, 1.0f);
			randomValues[i].y = RandF(-1.0f, 1.0f);
			randomValues[i].z = RandF(-1.0f, 1.0f);
			randomValues[i].w = RandF(-1.0f, 1.0f);
		}

    	D3D10_SUBRESOURCE_DATA initData;
    	initData.pSysMem = randomValues;
		initData.SysMemPitch = 1024 * sizeof(D3DXVECTOR4);
    	initData.SysMemSlicePitch = 1024 * sizeof(D3DXVECTOR4);
	
		// Create the texture.
		D3D10_TEXTURE1D_DESC textureDesc;
    	textureDesc.Width = 1024;
    	textureDesc.MipLevels = 1;
    	textureDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
    	textureDesc.Usage = D3D10_USAGE_IMMUTABLE;
    	textureDesc.BindFlags = D3D10_BIND_SHADER_RESOURCE;
    	textureDesc.CPUAccessFlags = 0;
    	textureDesc.MiscFlags = 0;
    	textureDesc.ArraySize = 1;

	    ID3D10Texture1D *randomTexture = 0;
    	HRESULT hr = mDevice->CreateTexture1D(&textureDesc, &initData, &randomTexture);
    	ErrorChecker(hr);
	
		// Create the resource view.
		D3D10_SHADER_RESOURCE_VIEW_DESC resourceViewDesc;
		resourceViewDesc.Format = textureDesc.Format;
    	resourceViewDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE1D;
    	resourceViewDesc.Texture1D.MipLevels = textureDesc.MipLevels;
		resourceViewDesc.Texture1D.MostDetailedMip = 0;
	
    	hr = mDevice->CreateShaderResourceView(randomTexture, &resourceViewDesc, &mRandomTextureRV);
    	ErrorChecker(hr);

		randomTexture->Release();

        delete[] randomValues;
	}
}
